from PyQt5.QtChart import (QAreaSeries, QBarSet, QChart, QChartView,
                           QLineSeries, QPieSeries, QScatterSeries, QSplineSeries,
                           QStackedBarSeries, QValueAxis)
from PyQt5.QtCore import QPoint, QPointF, Qt
from PyQt5.Qt import QApplication, QWidget, QPushButton, QThread, QMutex, pyqtSignal

from queue import Queue
import pyaudio
import numpy as np


class MyChartView(QChartView, Queue, QThread):
    # 暂停更新
    running: bool = False
    # 显示时长
    _displayDuration: float = 6.0
    # 采样率
    rates: int = 44100
    # x轴显示的值的范围
    rangeX: tuple = (0, rates * _displayDuration)
    rangeY: tuple = (-32768, 32768)

    @property
    def displayDuration(self) -> float:
        return self._displayDuration

    @displayDuration.setter
    def setDisplayDuration(self, seconds_duration: float):
        '''
        重新设置x轴的范围
        :param seconds_duration:
        :return:
        '''
        self.rangeX = (0, self.rates * self._displayDuration)
        self.axisX.setRange(*self.rangeX)

    def __init__(self, parent=None):
        # super(MyChartView, self).__init__(parent=parent)
        super(MyChartView, self).__init__(parent)
        Queue.__init__(self)
        # QThread.__init__(self, parent)
        QChartView.__init__(self, parent)
        # x坐标轴
        self.axisX = QValueAxis()
        self.axisX.setRange(*self.rangeX)
        # y轴
        self.axisY = QValueAxis()
        self.axisY.setRange(*self.rangeY)
        # 图表
        self.series = QLineSeries()
        # 设置标题
        self.series.setUseOpenGL(True)

        # 设置图表
        self.chart = QChart()
        # 图表标题
        self.chart.setTitle("实时音频")
        self.chart.addSeries(self.series)
        self.chart.addAxis(self.axisX, Qt.AlignBottom)
        self.chart.addAxis(self.axisY, Qt.AlignLeft)
        # 跟随坐标轴
        self.series.attachAxis(self.axisX)
        self.series.attachAxis(self.axisY)
        self.setChart(self.chart)

    def update(self):
        if not self.empty():
            sample_total_count, append_ponits = self.get()
            max_sample_size = self.rangeX[1]
            replace_count = max(self.series.count() + len(append_ponits) - max_sample_size, 0)
            temppoints = self.series.pointsVector()[replace_count:]
            temppoints.extend(append_ponits)
            self.series.replace(temppoints)
            self.task_done()


class ChartThread(QThread):
    # 更新事件的信号，连接到要在此线程执行的操作
    signal_update = pyqtSignal()

    def __init__(self, parent):
        super(ChartThread, self).__init__(parent)

    def run(self):
        while True:
            self.signal_update.emit()
            # 实时刷新界面
            QApplication.processEvents()


from PyQt5 import QtCore, QtGui, QtWidgets

import utils


class Ui_MainWindow(object):
    MainWindow: None
    devlist: list
    start = 0

    def __init__(self):
        self.pa = pyaudio.PyAudio()
        self.devlist = utils.getDevices()

    def setupUi(self, MainWindow):
        self.MainWindow = MainWindow
        self.MainWindow.setObjectName("MainWindow")
        self.MainWindow.setWindowTitle("MainWindow")
        self.MainWindow.resize(740, 580)
        self.centralwidget = QtWidgets.QWidget(self.MainWindow)
        self.centralwidget.setObjectName("centralwidget")

        self.chartView = MyChartView(self.MainWindow)
        self.chartView.setGeometry(QtCore.QRect(9, 59, 721, 471))
        self.chartView.setObjectName("chartView")
        self.chartThread = ChartThread(self.MainWindow)
        self.chartThread.signal_update.connect(self.chartView.update)
        self.chartThread.start()

        self.btn_startrecord = QtWidgets.QPushButton("开始录音", self.centralwidget)
        self.btn_startrecord.setGeometry(QtCore.QRect(170, 20, 75, 23))
        self.btn_startrecord.setObjectName("btn_startrecord")
        self.comboBox = QtWidgets.QComboBox(self.centralwidget)
        self.comboBox.setGeometry(QtCore.QRect(8, 20, 150, 22))
        self.comboBox.setObjectName("comboBox")
        # 添加到设备列表
        for dev in self.devlist:
            self.comboBox.addItem(dev['name'], dev['index'])
        self.MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 740, 23))
        self.menubar.setObjectName("menubar")
        self.MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(self.MainWindow)
        self.statusbar.setObjectName("statusbar")
        self.MainWindow.setStatusBar(self.statusbar)
        self.btn_stoprecord = QtWidgets.QPushButton("停止", self.MainWindow)
        self.btn_stoprecord.setGeometry(QtCore.QRect(260, 20, 75, 23))

        self.setupEvent()

        QtCore.QMetaObject.connectSlotsByName(self.MainWindow)

    def setupEvent(self):
        self.btn_startrecord.clicked.connect(self.startRecord)
        self.btn_stoprecord.clicked.connect(self.stopRecord)

    def startRecord(self):
        self.currentdev = self.devlist[self.comboBox.currentIndex()]
        print('当前设备：', self.currentdev)
        self.sample_total_count = 0

        self.stream = self.pa.open(input_device_index=self.currentdev['index'], format=pyaudio.paInt16,
                                   channels=1,
                                   rate=8000,
                                   input=True,
                                   frames_per_buffer=134,
                                   stream_callback=self.sound_record_callback)
        self.stream.start_stream()

    def sound_record_callback(self, in_data, frame_count, time_info, status):
        indexed_in_data = np.stack(
            [np.arange(self.sample_total_count, self.sample_total_count + frame_count),
             np.frombuffer(in_data, dtype=np.int16)], axis=1)
        append_ponits = list(map(lambda x: QPointF(*x), indexed_in_data))
        self.chartView.put((self.sample_total_count, append_ponits))
        self.sample_total_count += frame_count
        return (in_data, pyaudio.paContinue)

    def stopRecord(self, e):
        print(f"共采集了{self.sample_total_count}次")
        self.sample_total_count = 0
        self.stream.close()


if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)
    app.setStyle('Fusion')
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())
